#pragma once

#include "LinkedList.h"
#include "PlatformHelpers.h"
#include "ReplayMockedFunction.h"
#include "RegisterMacros.h"

namespace MockItNow
{
	class FunctionCallRecord;

	class Mocker : public MockerInterface
	{
	public:
	
		static Mocker& Instance();
		static bool IsAlive();

		Mocker(bool verifyOnDestruction = true);
		~Mocker();

		void Clear();

		template<typename R> REGISTER_FREE_FUNCTION(1, R, void);
		template<typename R, typename A1> REGISTER_FREE_FUNCTION(1, R, A1);
		template<typename R, typename A1, typename A2> REGISTER_FREE_FUNCTION(2, R, A1, A2);
		template<typename R, typename A1, typename A2, typename A3> REGISTER_FREE_FUNCTION(3, R, A1, A2, A3);
		template<typename R, typename A1, typename A2, typename A3, typename A4> REGISTER_FREE_FUNCTION(4, R, A1, A2, A3, A4);
		template<typename R, typename A1, typename A2, typename A3, typename A4, typename A5> REGISTER_FREE_FUNCTION(5, R, A1, A2, A3, A4, A5);
		template<typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6> REGISTER_FREE_FUNCTION(6, R, A1, A2, A3, A4, A5, A6);
		template<typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7> REGISTER_FREE_FUNCTION(7, R, A1, A2, A3, A4, A5, A6, A7);
		template<typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8> REGISTER_FREE_FUNCTION(8, R, A1, A2, A3, A4, A5, A6, A7, A8);
		template<typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9> REGISTER_FREE_FUNCTION(9, R, A1, A2, A3, A4, A5, A6, A7, A8, A9);

		template<typename T, typename R> REGISTER_MEMBER_FUNCTION(1, R, void);
		template<typename T, typename R, typename A1> REGISTER_MEMBER_FUNCTION(1, R, A1);
		template<typename T, typename R, typename A1, typename A2> REGISTER_MEMBER_FUNCTION(2, R, A1, A2);
		template<typename T, typename R, typename A1, typename A2, typename A3> REGISTER_MEMBER_FUNCTION(3, R, A1, A2, A3);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4> REGISTER_MEMBER_FUNCTION(4, R, A1, A2, A3, A4);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5> REGISTER_MEMBER_FUNCTION(5, R, A1, A2, A3, A4, A5);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6> REGISTER_MEMBER_FUNCTION(6, R, A1, A2, A3, A4, A5, A6);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7> REGISTER_MEMBER_FUNCTION(7, R, A1, A2, A3, A4, A5, A6, A7);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8> REGISTER_MEMBER_FUNCTION(8, R, A1, A2, A3, A4, A5, A6, A7, A8);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9> REGISTER_MEMBER_FUNCTION(9, R, A1, A2, A3, A4, A5, A6, A7, A8, A9);

		template<typename T, typename R> REGISTER_CONST_MEMBER_FUNCTION(1, R, void);
		template<typename T, typename R, typename A1> REGISTER_CONST_MEMBER_FUNCTION(1, R, A1);
		template<typename T, typename R, typename A1, typename A2> REGISTER_CONST_MEMBER_FUNCTION(2, R, A1, A2);
		template<typename T, typename R, typename A1, typename A2, typename A3> REGISTER_CONST_MEMBER_FUNCTION(3, R, A1, A2, A3);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4> REGISTER_CONST_MEMBER_FUNCTION(4, R, A1, A2, A3, A4);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5> REGISTER_CONST_MEMBER_FUNCTION(5, R, A1, A2, A3, A4, A5);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6> REGISTER_CONST_MEMBER_FUNCTION(6, R, A1, A2, A3, A4, A5, A6);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7> REGISTER_CONST_MEMBER_FUNCTION(7, R, A1, A2, A3, A4, A5, A6, A7);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8> REGISTER_CONST_MEMBER_FUNCTION(8, R, A1, A2, A3, A4, A5, A6, A7, A8);
		template<typename T, typename R, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6, typename A7, typename A8, typename A9> REGISTER_CONST_MEMBER_FUNCTION(9, R, A1, A2, A3, A4, A5, A6, A7, A8, A9);

		void SetFunctionName(const char* name);
		void DefaultStub();

		template<typename T>
		void DefaultStub(T value)
		{
			MOCKITNOW_ASSERT(m_state == Startup);
			MOCKITNOW_ASSERT(m_registeredFunctions.GetTail() != NULL);

			const FunctionInfo* lastRegisteredFunction = m_registeredFunctions.GetTail();

			if (lastRegisteredFunction->hasReturnValue == false)
				ReportFailure("Trying to set a default return value for a function with no return, use DefaultStub");

			FunctionCallRecord* record = new FunctionCallRecord(lastRegisteredFunction, 0, 0);
			record->IgnoreArguments();
			record->SetReturnValue(value);

			m_defaultReturnRecords.Add(record);
		}

		template<typename T>
		void DefaultThrowException(T value)
		{
			MOCKITNOW_ASSERT(m_state == Startup);
			MOCKITNOW_ASSERT(m_registeredFunctions.GetTail() != NULL);

			const FunctionInfo* lastRegisteredFunction = m_registeredFunctions.GetTail();

			FunctionCallRecord* record = new FunctionCallRecord(lastRegisteredFunction, 0, 0);
			record->IgnoreArguments();
			record->SetException(value);

			m_defaultReturnRecords.Add(record);
		}

		void Ignore(int arguments);
		void FailWhenCalled();

		template<typename T>
		void SetCompareFunction(int arguments, typename TypeInfo<T>::CompareFunction compareFunction)
		{
			MOCKITNOW_ASSERT(m_state == Recording);

			FunctionCallRecord* record = m_functionCallRecords.GetTail();

			if (record == 0)
			{
				SetFailureContext(NULL);
				ReportFailure("You can't call SetCompareFunction() before recording the function! Did you forget to register it? Is it overloaded?");
			}

			for (int i = 0; i < record->GetNumArguments(); ++i)
			{
				if ((arguments & (1 << i)) > 0)
					record->SetCompareFunction<T>(i, compareFunction);
			}
		}

		template<typename T>
		void Return(T value)
		{
			MOCKITNOW_ASSERT(m_state == Recording);
			
			if (m_functionCallRecords.GetTail() == 0)
			{
				SetFailureContext(NULL);
				ReportFailure("You can't call Return() before recording the function! Did you forget to register it? Is it overloaded?");
			}

			m_functionCallRecords.GetTail()->SetReturnValue<T>(value);
		}

		template<typename T>
		void ReturnAlways(T value)
		{
			Return(value);
			m_functionCallRecords.GetTail()->SetReturnAlways();
			m_functionCallRecords.GetTail()->IgnoreArguments();
		}

		template<typename T>
		void ThrowException(T value)
		{
			MOCKITNOW_ASSERT(m_state == Recording);
			
			if (m_functionCallRecords.GetTail() == 0)
			{
				SetFailureContext(NULL);
				ReportFailure("You can't call ThrowException() before recording the function! Did you forget to register it? Is it overloaded?");
			}

			m_functionCallRecords.GetTail()->SetException<T>(value);
			m_functionCallRecords.GetTail()->IgnoreArguments();
		}

		template<typename T>
		void Return(int arguments, T value)
		{
			MOCKITNOW_ASSERT(m_state == Recording);
			
			FunctionCallRecord* record = m_functionCallRecords.GetTail();

			if (record == 0)
			{
				SetFailureContext(NULL);
				ReportFailure("You can't call Return() before recording the function! Did you forget to register it? Is it overloaded?");
			}
			
			for (int i = 0; i < record->GetNumArguments(); ++i)
			{
				if ((arguments & (1 << i)) > 0)
					record->SetArgumentReturnValue<T>(i, value);
			}
		}

		void Record();
		void Replay();
		bool Verify();

		bool IsRecording() const;

		bool ShouldStubFunction(size_t address, size_t context);
		void SetCurrentFunctionAddress(size_t address);
		size_t GetFunctionJumpAddress() const;

		void CallOriginal();

		void SetVerifyOnDestruction(bool value);

		FunctionCallRecord* RegisterRecordFunctionCall(int numArguments, void* target);
		FunctionCallRecord* RegisterReplayFunctionCall(void* target);

	private:

		void RegisterFunction(size_t mockFunctionAddress, size_t recordFunctionAddress, size_t replayFunctionAddress, bool hasReturnValue);
		const FunctionInfo* FindFunctionInfo(size_t address) const;
		FunctionCallRecord* FindDefaultReturnFunctionCall(size_t address);
		FunctionCallRecord* FindReturnAlwaysFunctionCall(size_t address, size_t context);

		bool ShouldRecordFunctionCall(size_t address);
		bool ShouldReplayFunctionCall(size_t address, size_t context);
		bool HasDefaultReturn(size_t address);
		bool HasReturnAlways(size_t address, size_t context);

		enum State
		{
			Startup,
			Recording,
			Replaying,
			Verified
		};

		bool m_verifyOnDestruction;

		State m_state;

		const FunctionInfo* m_currentFunctionInfo;

		FunctionCallRecord* m_nextReplayFunctionCallRecord;

		LinkedList<FunctionInfo> m_registeredFunctions;
		LinkedList<FunctionCallRecord> m_functionCallRecords;
		LinkedList<FunctionCallRecord> m_defaultReturnRecords;

		static Mocker* m_instance;
	};
}
